"""
Test Configuration and Fixtures
Provides shared test fixtures and configuration for the test suite
"""

import pytest
import asyncio
import os
import tempfile
from typing import AsyncGenerator, Generator
from unittest.mock import Mock, MagicMock

import psycopg2
from fastapi.testclient import TestClient
from httpx import AsyncClient

# Import application components
from main import app
from config import get_config, AppConfig
from services.database_manager import DatabaseManager
from services.data_pipeline import DataPipeline

# Test configuration
TEST_DATABASE_URL = "postgresql://test_user:test_password@localhost:5432/test_arxiv_platform"

@pytest.fixture(scope="session")
def event_loop():
    """Create an instance of the default event loop for the test session."""
    loop = asyncio.get_event_loop_policy().new_event_loop()
    yield loop
    loop.close()

@pytest.fixture(scope="session")
def test_config() -> AppConfig:
    """Test configuration with test database and mock services"""
    config = get_config()
    
    # Override with test settings
    config.database.url = TEST_DATABASE_URL
    config.system.environment = "test"
    config.system.log_level = "DEBUG"
    config.scraper.scheduling_enabled = False
    config.features.enable_email_notifications = False
    
    return config

@pytest.fixture(scope="session")  
async def test_db():
    """Set up test database"""
    # Create test database
    try:
        # Connect to PostgreSQL server
        conn = psycopg2.connect(
            host="localhost",
            port=5432,
            user="postgres",
            password="password"
        )
        conn.autocommit = True
        
        with conn.cursor() as cur:
            # Drop test database if exists
            cur.execute("DROP DATABASE IF EXISTS test_arxiv_platform")
            # Create test database
            cur.execute("CREATE DATABASE test_arxiv_platform")
            # Create test user
            cur.execute("DROP USER IF EXISTS test_user")
            cur.execute("CREATE USER test_user WITH ENCRYPTED PASSWORD 'test_password'")
            cur.execute("GRANT ALL PRIVILEGES ON DATABASE test_arxiv_platform TO test_user")
        
        conn.close()
        
        # Run database schema
        test_conn = psycopg2.connect(TEST_DATABASE_URL)
        test_conn.autocommit = True
        
        # Read and execute schema
        schema_path = "arxiv_subscription_platform/database/schema.sql"
        if os.path.exists(schema_path):
            with open(schema_path, 'r') as f:
                schema_sql = f.read()
            
            with test_conn.cursor() as cur:
                cur.execute(schema_sql)
        
        test_conn.close()
        
        yield TEST_DATABASE_URL
        
        # Cleanup
        cleanup_conn = psycopg2.connect(
            host="localhost",
            port=5432,
            user="postgres", 
            password="password"
        )
        cleanup_conn.autocommit = True
        
        with cleanup_conn.cursor() as cur:
            cur.execute("DROP DATABASE IF EXISTS test_arxiv_platform")
            cur.execute("DROP USER IF EXISTS test_user")
        
        cleanup_conn.close()
        
    except Exception as e:
        pytest.skip(f"Could not set up test database: {e}")

@pytest.fixture
async def db_manager(test_config: AppConfig) -> AsyncGenerator[DatabaseManager, None]:
    """Database manager fixture"""
    db = DatabaseManager()
    # Override config
    db.config = test_config
    try:
        yield db
    finally:
        await db.close_connections()

@pytest.fixture
def client(test_config: AppConfig) -> Generator[TestClient, None, None]:
    """Test client for FastAPI application"""
    with TestClient(app) as client:
        yield client

@pytest.fixture
async def async_client(test_config: AppConfig) -> AsyncGenerator[AsyncClient, None]:
    """Async test client for FastAPI application"""
    async with AsyncClient(app=app, base_url="http://test") as client:
        yield client

@pytest.fixture
def mock_scraper_service():
    """Mock ArXiv scraper service"""
    mock_service = Mock()
    mock_service.run_incremental_scrape.return_value = {
        "papers_processed": 5,
        "papers": [
            {
                "arxiv_id": "2024.12345",
                "title": "Test Paper 1", 
                "authors": ["Test Author 1"],
                "abstract": "This is a test paper abstract",
                "categories": ["cs.AI"],
                "published_date": "2024-01-01",
                "pdf_url": "https://arxiv.org/pdf/2024.12345.pdf"
            },
            {
                "arxiv_id": "2024.12346",
                "title": "Test Paper 2",
                "authors": ["Test Author 2", "Test Author 3"], 
                "abstract": "Another test paper abstract",
                "categories": ["math.NA"],
                "published_date": "2024-01-02",
                "pdf_url": "https://arxiv.org/pdf/2024.12346.pdf"
            }
        ]
    }
    return mock_service

@pytest.fixture
def mock_ai_engine():
    """Mock AI classification engine"""
    mock_engine = Mock()
    mock_engine.classify_paper.return_value = {
        "semantic_topics": ["machine learning", "neural networks"],
        "technical_categories": ["supervised learning"],
        "methodologies": ["deep learning"],
        "application_domains": ["computer vision"],
        "confidence_scores": {
            "machine learning": 0.95,
            "neural networks": 0.87
        },
        "metadata": {"processing_time": 0.5}
    }
    return mock_engine

@pytest.fixture
def mock_recommendation_engine():
    """Mock recommendation engine"""
    mock_engine = Mock()
    mock_engine.get_recommendations.return_value = [
        {
            "paper_id": "test_paper_1",
            "score": 0.95,
            "reasons": ["matches your interests in machine learning"],
            "rank": 1
        },
        {
            "paper_id": "test_paper_2", 
            "score": 0.87,
            "reasons": ["similar to papers you've saved"],
            "rank": 2
        }
    ]
    return mock_engine

@pytest.fixture
def mock_email_manager():
    """Mock email manager"""
    mock_manager = Mock()
    mock_manager.send_email.return_value = {
        "success": True,
        "message_id": "test_message_123",
        "sent_at": "2024-01-01T12:00:00Z"
    }
    mock_manager.check_smtp_connection.return_value = True
    return mock_manager

@pytest.fixture
def sample_user_data():
    """Sample user data for testing"""
    return {
        "email": "test@example.com",
        "password": "TestPassword123!",
        "full_name": "Test User",
        "display_name": "Test",
        "gdpr_consent": True,
        "marketing_consent": False
    }

@pytest.fixture
def sample_paper_data():
    """Sample paper data for testing"""
    return {
        "arxiv_id": "2024.12345",
        "title": "A Novel Approach to Machine Learning",
        "authors": ["John Doe", "Jane Smith"],
        "abstract": "This paper presents a novel approach to machine learning that achieves state-of-the-art results on several benchmarks.",
        "categories": ["cs.LG", "cs.AI"],
        "published_date": "2024-01-15",
        "pdf_url": "https://arxiv.org/pdf/2024.12345.pdf",
        "primary_category": "cs.LG"
    }

@pytest.fixture
def sample_user_preferences():
    """Sample user preferences for testing"""
    return {
        "paper_preferences": {
            "categories": ["cs.AI", "cs.LG", "math.NA"],
            "keywords": ["machine learning", "neural networks", "optimization"],
            "authors": ["Geoffrey Hinton", "Yann LeCun"],
            "institutions": ["Stanford University", "MIT"]
        },
        "notification_preferences": {
            "email_notifications": True,
            "weekly_digest": True,
            "instant_alerts": False,
            "digest_frequency": "weekly"
        }
    }

@pytest.fixture
async def data_pipeline(test_config: AppConfig, mock_scraper_service, mock_ai_engine) -> DataPipeline:
    """Data pipeline fixture with mocked services"""
    pipeline = DataPipeline()
    
    # Replace services with mocks
    pipeline.scraper_service = mock_scraper_service
    pipeline.ai_engine = mock_ai_engine
    
    return pipeline

@pytest.fixture
def temp_file():
    """Temporary file fixture"""
    with tempfile.NamedTemporaryFile(mode='w', delete=False) as f:
        yield f.name
    
    # Cleanup
    try:
        os.unlink(f.name)
    except FileNotFoundError:
        pass

@pytest.fixture
def temp_dir():
    """Temporary directory fixture"""
    with tempfile.TemporaryDirectory() as temp_dir:
        yield temp_dir

# Test data constants
TEST_ARXIV_IDS = [
    "2024.12345",
    "2024.12346", 
    "2024.12347",
    "2024.12348",
    "2024.12349"
]

TEST_CATEGORIES = [
    "cs.AI",
    "cs.LG", 
    "cs.CV",
    "math.NA",
    "math.AP",
    "physics.comp-ph"
]

TEST_AUTHORS = [
    "Geoffrey Hinton",
    "Yann LeCun", 
    "Yoshua Bengio",
    "Andrew Ng",
    "Fei-Fei Li"
]

# Helper functions for tests
def create_test_user_in_db(db_conn, user_data):
    """Helper to create test user in database"""
    with db_conn.cursor() as cur:
        cur.execute("""
            INSERT INTO user_profiles (user_id, email, full_name, display_name, gdpr_consent)
            VALUES (%s, %s, %s, %s, %s)
            RETURNING id
        """, (
            user_data.get("user_id", "test_user_123"),
            user_data["email"],
            user_data["full_name"],
            user_data.get("display_name"),
            user_data.get("gdpr_consent", True)
        ))
        return cur.fetchone()['id']

def create_test_paper_in_db(db_conn, paper_data):
    """Helper to create test paper in database"""
    import json
    with db_conn.cursor() as cur:
        cur.execute("""
            INSERT INTO papers (arxiv_id, title, authors, abstract, categories, published_date, pdf_url, primary_category)
            VALUES (%s, %s, %s, %s, %s, %s, %s, %s)
            RETURNING id
        """, (
            paper_data["arxiv_id"],
            paper_data["title"],
            json.dumps(paper_data["authors"]),
            paper_data["abstract"],
            json.dumps(paper_data["categories"]),
            paper_data["published_date"],
            paper_data["pdf_url"],
            paper_data.get("primary_category")
        ))
        return cur.fetchone()['id']

# Pytest configuration
def pytest_configure(config):
    """Configure pytest with custom markers"""
    config.addinivalue_line(
        "markers", 
        "integration: mark test as integration test (requires external services)"
    )
    config.addinivalue_line(
        "markers",
        "slow: mark test as slow running"
    )
    config.addinivalue_line(
        "markers", 
        "unit: mark test as unit test (isolated)"
    )

# Custom assertions
def assert_valid_user_profile(profile_data):
    """Assert that user profile data is valid"""
    assert "id" in profile_data
    assert "email" in profile_data
    assert "full_name" in profile_data
    assert "subscription_status" in profile_data
    assert "created_at" in profile_data

def assert_valid_paper_data(paper_data):
    """Assert that paper data is valid"""
    assert "id" in paper_data
    assert "arxiv_id" in paper_data
    assert "title" in paper_data
    assert "authors" in paper_data
    assert "abstract" in paper_data
    assert "categories" in paper_data
    assert "published_date" in paper_data

def assert_api_response_success(response, expected_fields=None):
    """Assert API response is successful and contains expected fields"""
    assert response.status_code == 200
    data = response.json()
    assert "success" in data
    assert data["success"] is True
    
    if expected_fields:
        for field in expected_fields:
            assert field in data, f"Expected field '{field}' not found in response"